package org.apache.ocean.name;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ocean.ClusterConstants;
import org.apache.ocean.ClusterUtil;
import org.apache.ocean.OceanXML;
import org.apache.ocean.util.Base26Coder;
import org.apache.ocean.util.PrintStringWriter;
import org.jdom.Element;

/**
 * @author jasonr
 */
// TODO: test out using 2 servers by using run on server inside eclipse
// TODO: test out using 3 servers
public class NameServlet extends HttpServlet {
	public static Logger log = Logger.getLogger(NameServlet.class.getName());
	private NameService nameService;
	private SQLNameDatabase nameDatabase;

	public NameServlet() {
	}

	public static void main(String[] args) {
		System.out.println(new Date(0));
	}

	public void destroy() {
		log.info("destroy started");
		try {
			nameService.stop();
		} catch (Throwable throwable) {
			log.log(Level.SEVERE, "", throwable);
		}
		super.destroy();
		log.info("destroy finished");
	}

	public void init() throws ServletException {
		try {
			nameService = NameService.getNameService();
			nameService.start();
			nameDatabase = nameService.nameDatabase;
			log.info("NameServlet.init finished");
		} catch (Throwable throwable) {
			log.log(Level.SEVERE, "", throwable);
			throw new ServletException(throwable);
		}
	}

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		myService(request, response);
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		myService(request, response);
	}

	public void myService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		PrintStringWriter writer = new PrintStringWriter();
		boolean writeString = true;
		try {
			response.setStatus(200);
			if (StringUtils.equalsIgnoreCase("POST", request.getMethod())) {
				OceanXML requestXML = OceanXML.parse(request.getReader());
				String action = requestXML.getHeaderValue("action");
				log.info("action: " + action);
				if (StringUtils.equals("reportnode", action)) {
					handleReportNode(requestXML, writer);
				}
			} else {
				// log.info("queryString: "+request.getQueryString());
				String action = request.getParameter("action");

				if (StringUtils.equals("getnameurls", action)) {
					handleGetNameUrls(request, response, writer);
				} else if (StringUtils.equals("getnodes", action)) {
					handleGetNodes(request, response, writer);
				} else if (StringUtils.equals("getfile", action)) {
					handleGetFile(request, response);
					writeString = false;
				} else if (StringUtils.equals("getfilelist", action)) {
					handleGetFileList(request, response, writer);
				} else if (StringUtils.equals("getreportednameinfos", action)) {
					handleGetReportedNameInfos(request, response, writer);
				} else if (StringUtils.equals("savenodechanges", action)) {
					handleSaveNodeChanges(request, response, writer);
					handleDisplayMainUI(request, response, writer);
				} else {
					handleDisplayMainUI(request, response, writer);
				}
			}
		} catch (Throwable throwable) {
			log.log(Level.SEVERE, "", throwable);
			try {
				writer = new PrintStringWriter();
				writer.write(OceanXML.toOceanXMLString(throwable));
			} catch (Throwable exTh) {
				log.log(Level.SEVERE, "inside creating exception response", exTh);
				throw new ServletException(exTh);
			}
		} finally {
			if (writeString)
				response.getWriter().write(writer.toString());
		}
	}

	private void handleSaveNodeChanges(HttpServletRequest request, HttpServletResponse response, PrintStringWriter writer) throws Exception {
		for (String string : ClusterUtil.toList(request.getParameterValues("nodeid-cellid"))) {
			String[] split = StringUtils.split(string, ":");
			String nodeID = split[0];
			String cellID = split[1];
			Node set = new Node();
			set.id = nodeID;
			set.assignedCellID = cellID;
			set.replicationID = nameService.getNextReplicationID();
			nameService.nameDatabase.updateNodeSet(set, false);
		}
	}

	private void handleReportNode(OceanXML request, PrintStringWriter writer) throws Exception {
		Node reportedNode = (Node) request.getFirstInBody(new Node.Transform());
		if (reportedNode.id == null) {
			throw new Exception("id missing");
		}
		log.info("reportedNode: " + reportedNode);
		if (reportedNode.assignedCellID != null) {
			throw new Exception("cannot contain assignedCellID");
		} else if (reportedNode.assignedStatus != null) {
			throw new Exception("cannot contain assignedStatus");
		} else if (reportedNode.dateCreated != null) {
			throw new Exception("cannot contain dateCreated");
		} else if (reportedNode.dateModified != null) {
			throw new Exception("cannot contain dateModified");
		} else if (reportedNode.reportedMasterIDSinceDate != null) {
			throw new Exception("cannot contain reportedMasterIDSinceDate");
		}
		reportedNode.reportDate = new Date();
		Node existingNode = nameDatabase.findNode(reportedNode.id);
		if (existingNode != null) {
			if (reportedNode.reportedMasterID != null) {
				if (existingNode.reportedMasterIDSinceDate == null || !StringUtils.equals(reportedNode.reportedMasterID, existingNode.reportedMasterID)) {
					reportedNode.reportedMasterIDSinceDate = new Date();
				}
			}
			// if reportedStatus changed, change the since date
			if (!StringUtils.equals(existingNode.reportedStatus, reportedNode.reportedStatus)) {
				reportedNode.reportedStatusSinceDate = new Date();
			}
		}
		if (reportedNode.reportedMasterID == null) {
			reportedNode.reportedMasterID = ClusterConstants.NULL_STRING;
			reportedNode.reportedMasterIDSinceDate = ClusterConstants.NULL_DATE;
		}
		if (reportedNode.reportedStatus == null) {
			reportedNode.reportedStatusSinceDate = ClusterConstants.NULL_DATE;
		}
		Node updateNode = null;
		if (nameDatabase.nodeExistsByID(reportedNode.id)) {
			updateNode = nameDatabase.updateNodeSet(reportedNode, true);
		} else {
			reportedNode.replicationID = nameService.getNextReplicationID();
			updateNode = nameDatabase.updateNode(reportedNode);
		}
		log.info("response node: " + updateNode);
		OceanXML.write(nameService.getServerInfo(), updateNode, writer);
	}

	private void addNextCellID(SortedSet<String> distinctCellIDs) {
		if (distinctCellIDs != null && distinctCellIDs.size() > 0) {
			String lastID = distinctCellIDs.last();
			Base26Coder base26Coder = new Base26Coder(true, false);
			long i = base26Coder.decode(lastID);
			long nexti = i + 1;
			String nextID = base26Coder.encode(nexti);
			distinctCellIDs.add(nextID);
		} else {
			distinctCellIDs.add("A");
		}
	}

	private String getNodeURLString(String id) throws Exception {
		if (StringUtils.isBlank(id))
			return "";
		Node masterNode = nameDatabase.findNode(id);
		if (masterNode != null && masterNode.url != null) {
			return masterNode.url.toString();
		}
		return "";
	}

	private void handleDisplayMainUI(HttpServletRequest request, HttpServletResponse response, PrintStringWriter writer) throws Exception {
		response.setContentType(ClusterConstants.CONTENT_TYPE_HTML);
		SortedSet<String> distinctCellIDs = new TreeSet<String>(nameDatabase.findDistinctCellIDs());
		addNextCellID(distinctCellIDs);
		writer.println("<form action='" + request.getRequestURI() + "' method=get>");
		writer.println("<input type=hidden name=action value=savenodechanges>");
		writer.println("Nodes<br>");
		writer.println("<table border=1>");
		writer.println("<tr><td>url</td><td>transaction id</td><td>reported master</td><td>assigned cellid</td><td>reported cellid</td><td>reported status</td><td>last reported</td><td>date created</td></tr>");
		List<Node> nodes = nameDatabase.findNodes("assignedCellID ASC, url ASC");
		for (Node node : nodes) {
			String url = ClusterUtil.toString(node.url);
			String assignedCellID = ClusterUtil.toString(node.assignedCellID);
			String reportedCellID = ClusterUtil.toString(node.reportedCellID);
			String reportedStatus = ClusterUtil.toString(node.reportedStatus);
			if (StringUtils.isNotBlank(reportedStatus) && node.reportedStatusSinceDate != null) {
				reportedStatus = reportedStatus+" "+ClusterUtil.toDurationWords(node.reportedStatusSinceDate);
			}
			String highestCellTransactionIDDatabase = ClusterUtil.toString(node.highestCellTransactionIDDatabase);
			String reportedMasterURL = getNodeURLString(node.reportedMasterID);
			String reportedMaster = reportedMasterURL;
			if (StringUtils.isNotBlank(reportedMasterURL) && node.reportedMasterIDSinceDate != null) {
				reportedMaster = reportedMaster + " " + ClusterUtil.toDurationWords(node.reportedMasterIDSinceDate);
			}
			String lastReportedDuration = ClusterUtil.toDurationWords(node.reportDate);
			String dateCreatedDuration = ClusterUtil.toDurationWords(node.dateCreated);
			writer.println("<tr><td>" + url + "</td><td>" + highestCellTransactionIDDatabase + "</td><td>" + reportedMaster + "</td>");
			if (StringUtils.isBlank(assignedCellID)) {
				writer.print("<td>");
				if (distinctCellIDs.size() > 0) {
					writer.println("<select name='nodeid-cellid'>");
					for (String optionCellID : distinctCellIDs) {
						writer.println("<option value='" + node.id + ":" + optionCellID + "'>" + optionCellID + "</option>");
					}
					writer.println("</select>");
				}
				writer.print("</td>");
			} else {
				writer.println("<td>" + assignedCellID + "</td>");
			}
			writer.println("<td>" + reportedCellID + "</td>");
			writer.println("<td>" + reportedStatus + "</td>");
			writer.println("<td>" + lastReportedDuration + "</td>");
			writer.println("<td>" + dateCreatedDuration + "</td>");
			writer.println("</tr>");
		}
		writer.println("</table>");
		writer.println("<input type=submit name=save value=Save>");
		writer.println("</form>");
	}

	private void handleGetNameUrls(HttpServletRequest request, HttpServletResponse response, PrintStringWriter writer) throws Exception {
		response.setContentType(ClusterConstants.CONTENT_TYPE_XML);
		Set<URL> urls = nameService.nameURLs;
		List<Element> elements = new ArrayList<Element>();
		for (URL url : urls) {
			Element urlElement = new Element("nameurl");
			urlElement.setText(url.toString());
			elements.add(urlElement);
		}
		OceanXML.write(nameService.getServerInfo(), elements, writer);
	}

	private void handleGetReportedNameInfos(HttpServletRequest request, HttpServletResponse response, PrintStringWriter writer) throws Exception {
		response.setContentType(ClusterConstants.CONTENT_TYPE_XML);
		ReportedNameInfos reportedNameInfos = nameService.getReportedNameInfos();
		OceanXML.write(nameService.getServerInfo(), reportedNameInfos, writer);
	}

	private void handleGetFileList(HttpServletRequest request, HttpServletResponse response, PrintStringWriter writer) throws Exception {
		response.setContentType(ClusterConstants.CONTENT_TYPE_XML);
		File[] files = nameService.nameConfig.filesDirectory.listFiles();
		List<FileClient.File> fileClientFiles = new ArrayList<FileClient.File>(files.length);
		for (File file : files) {
			FileClient.File fileClientFile = new FileClient.File();
			fileClientFile.lastModified = new Date(file.lastModified());
			fileClientFile.path = file.getName();
			fileClientFiles.add(fileClientFile);
		}
		OceanXML.write(nameService.getServerInfo(), fileClientFiles, writer);
	}

	private void handleGetFile(HttpServletRequest request, HttpServletResponse response) throws Exception {
		String path = request.getParameter("path");
		if (StringUtils.isBlank(path))
			response.setStatus(404);
		File file = new File(nameService.nameConfig.filesDirectory, path);
		if (!file.exists()) {
			log.info(file.getAbsolutePath() + " does not exist");
			response.setStatus(404);
		} else {
			IOUtils.copy(FileUtils.openInputStream(file), response.getOutputStream());
		}
	}

	private void handleGetNodes(HttpServletRequest request, HttpServletResponse response, PrintStringWriter writer) throws Exception {
		String cellID = request.getParameter("cellid");
		List<Node> nodes = null;
		if (StringUtils.isBlank(cellID)) {
			nodes = nameDatabase.findNodes();
		} else {
			nodes = nameDatabase.findNodesByAssignedCellID(cellID);
		}
		response.setContentType(ClusterConstants.CONTENT_TYPE_XML);
		OceanXML.write(nameService.getServerInfo(), nodes, writer);
	}
}
